using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using DragonAwakening.Game;
using System.Text;
using System.IO;
using DragonAwakening.Core.Graphics;
using DragonAwakening.Graphics;
using DragonAwakening.Core.Game;

namespace DragonAwakening
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        private static bool ShowFPS = true;
        private int fpsCounter = 0;
        private double fps = 0;

        private const int MessageDisplayTime = 1500;
        private const int GameTurnTime = 1000;

        GraphicsDeviceManager graphics;
       
        GameInstance game;
        SpriteFont font;
        SpriteFont messageFont;
        
        Texture2D buttonTexture;
        Texture2D blank;
        Texture2D black;

        TextureManager manager;
        SceneManager3D scene;

        ButtonState prevState;

        List<KeyValuePair<string, float>> messageList = new List<KeyValuePair<string, float>>();

        string currentMessage;
        int leftMessageTime;

        IList<IClickTarget> clickProcessors = new List<IClickTarget>();

        /// <summary>
        /// Device view port
        /// </summary>
        private WinPhoneViewPort viewPort;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft;

            Content.RootDirectory = "Content";

            // Frame rate is 30 fps by default for Windows Phone.
            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Extend battery life under lock.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            this.game = GameInstance.Create();

            var human = new Character("Aldaren", RaceName.Human, CharacterClass.Paladin, Sex.Male);
            this.game.Group.Add(human);

            var elf = new Character("Davalmak", RaceName.HalfOrc, CharacterClass.Rogue, Sex.Female);
            this.game.Group.Add(elf);

            var elf2 = new Character("Elfius", RaceName.Dwarf, CharacterClass.Cleric, Sex.Male);
            this.game.Group.Add(elf2);

            var elf3 = new Character("Magus", RaceName.Elf, CharacterClass.Wizard, Sex.Female);
            this.game.Group.Add(elf3);

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            this.font = this.Content.Load<SpriteFont>("BaseFont");
            this.messageFont = this.Content.Load<SpriteFont>("Message");
            this.buttonTexture = this.Content.Load<Texture2D>("buttonBar");

            this.manager = new TextureManager(this.Content, this.GraphicsDevice);
            this.viewPort = new WinPhoneViewPort(GraphicsDevice, this.manager);

            this.blank = this.manager.GetColorTexture(Color.White);
            this.black = this.manager.GetColorTexture(Color.Gray);

            var characterView = new CharacterView(this.font, this.manager, this.game);
            var characterInfoView = new CharacterInfoView(this.font, this.manager, this.game);

            // views initialization
            this.game.LoadMap(this.Content);
            this.game.viewsContainer.Add(typeof(IEnumerable<Character>), characterView);
            this.game.viewsContainer.Add(typeof(Character), characterInfoView);

            // click processors initialization
            this.clickProcessors.Add(characterView);
            this.clickProcessors.Add(characterInfoView);

            //this.scene = new SceneManager(this.manager);
            this.scene = new SceneManager3D(this.manager, this.GraphicsDevice, this.graphics, this.game, this.Content);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allow the game to exit.
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            this.game.UpdateGameState(this.messageList, gameTime, GameTurnTime);
            this.ProcessInput(gameTime);

            if (this.leftMessageTime > 0)
            {
                this.leftMessageTime -= gameTime.ElapsedGameTime.Milliseconds;
            }
            else if (this.messageList.Count > 0)
            {
                var messageInfo = messageList[messageList.Count - 1];
                this.messageList.RemoveAt(messageList.Count - 1);

                this.currentMessage = messageInfo.Key;
                this.leftMessageTime = (int)(MessageDisplayTime * messageInfo.Value);
            }

            if (ShowFPS && gameTime.ElapsedGameTime.TotalSeconds > 0)
            {
                fps = fpsCounter / gameTime.ElapsedGameTime.TotalSeconds;
                fpsCounter = 0;
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Black);
            graphics.GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;

            this.viewPort.batch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
            this.scene.DrawScene(graphics.GraphicsDevice);

            var characterView = this.game.GetView<IEnumerable<Character>>();
            characterView.Draw(this.viewPort, this.game.Group);

            if (this.game.CurrentState.Is<CharacterStatsGameState>())
            {
                var currentCharacter = ((CharacterStatsGameState)this.game.CurrentState).Character;
                var characterInfoView = this.game.GetView<Character>();
                characterInfoView.Draw(this.viewPort, currentCharacter);
            }
            else
            {
                DrawControls();
            }

            if (this.leftMessageTime > 0)
            {
                DrawMessages();
            }

            if (ShowFPS)
            { 
                fpsCounter++;
                this.viewPort.batch.DrawString(this.font, fps.ToString("F1"), new Vector2(750, 460), Color.White);
            }

            this.viewPort.batch.End();
            base.Draw(gameTime);
        }

        private void DrawControls()
        {
            for (int i = 0; i < 6; i++)
            {
                this.viewPort.batch.Draw(this.buttonTexture, new Rectangle(700, i * 60 + 1, 100, 58), Color.White);
            }
        }
        
        private void DrawMessages()
        {
            if (!string.IsNullOrWhiteSpace(this.currentMessage))
            {
                int boxWidth = 520;
                int boxHeight = 40;

                var messageBox = this.messageFont.MeasureString(this.currentMessage);

                List<string> displayStrings = new List<string>();
                
                if (messageBox.X > boxWidth)
                {
                    // split string
                    var tokens = this.currentMessage
                        .Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
                        .Select(x => new KeyValuePair<string, float>(x, this.messageFont.MeasureString(x).X))
                        .ToArray();

                    StringBuilder builder = new StringBuilder(this.currentMessage.Length);
                    int index = 0;

                    string space = " ";
                    float spaceLength = this.messageFont.MeasureString(space).X;

                    float accumulatedWidth = 0.0F;
                    while (index < tokens.Length)
                    {
                        var tokenItem = tokens[index++];
                        float expectedWidth = accumulatedWidth + tokenItem.Value;
                        if (expectedWidth > boxWidth)
                        {
                            displayStrings.Add(builder.ToString());
                            builder.Clear();
                            builder.Append(tokenItem.Key + space);
                            accumulatedWidth = tokenItem.Value + spaceLength;
                        }
                        else
                        {
                            builder.Append(tokenItem.Key + space);
                            accumulatedWidth += tokenItem.Value + spaceLength;
                        }
                    }

                    displayStrings.Add(builder.ToString());
                    boxHeight = (int)(displayStrings.Count * messageBox.Y);
                }
                else
                {
                    displayStrings.Add(this.currentMessage);
                    boxHeight = (int)messageBox.Y;
                }

                
                Rectangle drawBox = new Rectangle(140, 465 - boxHeight, boxWidth, boxHeight);

                this.viewPort.batch.Draw(this.black, drawBox, null, new Color(100, 100, 100, 150), 0.0F, new Vector2(0, 0), SpriteEffects.None, 0.1F);

                this.viewPort.batch.Draw(this.black, new Rectangle(drawBox.X, drawBox.Y, drawBox.Width, 3), null, Color.White, 0.0F, new Vector2(0, 0), SpriteEffects.None, 0.1F);
                this.viewPort.batch.Draw(this.black, new Rectangle(drawBox.X, drawBox.Y + drawBox.Height, drawBox.Width, 3), null, Color.White, 0.0F, new Vector2(0, 0), SpriteEffects.None, 0.1F);
                this.viewPort.batch.Draw(this.black, new Rectangle(drawBox.X, drawBox.Y, 3, drawBox.Height), null, Color.White, 0.0F, new Vector2(0, 0), SpriteEffects.None, 0.1F);
                this.viewPort.batch.Draw(this.black, new Rectangle(drawBox.X + drawBox.Width, drawBox.Y, 3, drawBox.Height), null, Color.White, 0.0F, new Vector2(0, 0), SpriteEffects.None, 0.1F);

                int offset = drawBox.Y + 10;
                foreach (var s in displayStrings)
                {
                    this.viewPort.batch.DrawString(this.messageFont, s, new Vector2(drawBox.X + 5, offset), Color.Orange);
                    offset += (int)messageBox.Y;
                }
            }
        }

        private void ProcessInput(GameTime gameTime)
        {
            var mouseState = Mouse.GetState();
            var clicked = mouseState.LeftButton == ButtonState.Released && this.prevState == ButtonState.Pressed;

            if (clicked)
            { 
                var processed = this.clickProcessors.Any(x => x.OnClick(new Point(mouseState.X, mouseState.Y), this.game));
                if (!processed)
                {
                    Rectangle forwardRect = new Rectangle(100, 0, 700, 239);
                    Rectangle leftRect = new Rectangle(100, 240, 400, 480);
                    Rectangle rightRect = new Rectangle(401, 240, 700, 480);

                    int angle = 0;
                    bool canMove = this.game.CurrentState.GetType() == typeof(GameState);
                    if (canMove )
                    {
                        if (forwardRect.Contains(mouseState.X, mouseState.Y))
                        {
                            if (!this.game.MoveFroward())
                            {
                                messageList.Add(new KeyValuePair<string, float>("Ouch", 1.0F));
                            }
                        }
                        if (leftRect.Contains(mouseState.X, mouseState.Y))
                        {
                            angle = 3;
                        }
                        else if (rightRect.Contains(mouseState.X, mouseState.Y))
                        {
                            angle = 1;
                        }

                        this.game.direction = (Sides)(((int)this.game.direction + angle) % 4);
                    }
                }
            }

            this.prevState = mouseState.LeftButton;
        }
    }
}